/**
 * Copyright [2019] [LiBo/Alex of copyright liboware@gmail.com ]
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package report.fallback.imol;

import com.google.common.base.Joiner;
import com.lhy.report.bean.FallbackDomain;
import com.lhy.report.bean.delayed.ExecuteInvokerEvent;
import com.lhy.report.delayed.redis.DelayedRedissionClientTool;
import com.lhy.report.fallback.FallbackDataProcessStrategy;
import com.lhy.report.fallback.RetryFallbackProcessStrategy;
import com.lhy.report.redission.RedissonClientTool;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RAtomicLong;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * @project-name:wiz-sound-ai6
 * @package-name:com.wiz.soundai.callservice.gateway.manager.fallback.impl
 * @author:LiBo/Alex
 * @create-date:2021-11-16 12:50
 * @copyright:libo-alex4java
 * @email:liboware@gmail.com
 * @description: 重试故障恢复操作处理的策略执行器
 */
@Slf4j
@Component
public class DefaultRetryFallbackProcessStrategy implements RetryFallbackProcessStrategy<FallbackDomain, FallbackDataProcessStrategy<Object,Boolean>> {

    /**
     * 延时队列
     */
    public static final String DEFAULT_RETRY_EXECUTE_TASK_QUEUE = "RETRY_EXECUTE_PROCESS_QUEUE";

    /**
     * 执行计数器
     */
    public static final String DEFAULT_RETRY_EXECUTE_TASK_COUNTER = "RETRY_EXECUTE_PROCESS_COUNTER";

    /**
     * redissionClient
     */
    @Autowired(required = false)
    DelayedRedissionClientTool redissionClientTool;

    /**
     * redissionClient
     */
    @Autowired(required = false)
    RedissonClientTool redissonClientTool;

    /**
     * 重试fallback的间隔时间（秒）
     */
    @Value("${retry.fallback.interval:5}")
    private Long retryFallbackInterval;

    /**
     * 重试fallback的间隔次数(3次重试)
     */
    @Value("${retry.fallback.times:3}")
    private Integer retryFallbackTimes;

    /**
     * 连接处理器操作机制
     */
    final Joiner joiner = Joiner.on(":").skipNulls();

    /**
     * 重试处理操作控制机制
     * 考虑到消息的不确定因素，因此 process操作一般为不可靠方式+fallback可靠处理方式，fallback一般可以考虑取做充实，mq可能存在落盘导致broker失败后，再去重试，会存在幂等性问题！
     * 所以默认实现灭有进行process处理重试操作。
     * @param param
     * @param objectBooleanFallbackDataProcessStrategy
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public boolean process(FallbackDomain param, FallbackDataProcessStrategy objectBooleanFallbackDataProcessStrategy) {
        // fallbackResult 故障失败结果，程序正常处理结果
        Boolean fallbackResult = Boolean.TRUE, processResult = Boolean.FALSE;
        try {
            // 是否执行process
            if(param.isExecProcess()) {
                processResult = (Boolean) objectBooleanFallbackDataProcessStrategy.process(param.getModel());
            }
            // 如果判断的结果为空或者false，则说明执行处理发送失败，切换至MQ版本发送操作处理
            if (Objects.isNull(processResult) || processResult.booleanValue() == Boolean.FALSE) {
                log.info("execute execute process operation , the result is failure,prepared the retry process! data:{}",param);
                fallbackResult = (Boolean) objectBooleanFallbackDataProcessStrategy.fallback(null, param.getModel());
                // 如果执行失败！
                if (!fallbackResult) {
                    throw new RuntimeException("execute fallback result is failure!");
                }
            }
            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("execute fallback process is failure!",e);
            if(shouldRetry(param,fallbackResult)){
                log.info("execute the retry fallback process!");
                ExecuteInvokerEvent executeInvokerEvent = new ExecuteInvokerEvent(param.getUuid(),DEFAULT_RETRY_EXECUTE_TASK_QUEUE);
                executeInvokerEvent.setDataModel(param);
                // 此处需要主要可以控制是否执行支持重试process操作
                param.setExecProcess(param.isNeedRetryProcess());
                executeInvokerEvent.setDelayedTime(retryFallbackInterval);
                executeInvokerEvent.setTimeUnit(TimeUnit.SECONDS);
                try {
                    redissionClientTool.offer(executeInvokerEvent);
                } catch (Exception e1) {
                    log.error("push the delayed queue is failure!",e1);
                    return Boolean.FALSE;
                }
            }
            return Boolean.FALSE;
        }
    }


    /**
     * 应该进行重试操作处理控制
     * @param param
     * @param lastFallBackResult
     * @return
     */
    @Override
    public boolean shouldRetry(FallbackDomain param, Boolean lastFallBackResult) {
        // 如果结果为null或者结果为false失败！
        if(Objects.isNull(lastFallBackResult) || lastFallBackResult.booleanValue() == Boolean.FALSE){
            try {
                log.info("should retry execute retry process");
                String counterName = joiner.join(DEFAULT_RETRY_EXECUTE_TASK_COUNTER,param.getProcessType(),param.getUuid());
                log.info("retry the counter Name:{}",counterName);
                RAtomicLong atomicLong = redissonClientTool.getRedissonClient().getAtomicLong(counterName);
                // 获取计数器数据信息:用于区分首次进行计算和最终放弃重试的状态标志！
                if(atomicLong.get() < 0){
                    log.info("justify the counter is less than 0 !");
                    return Boolean.FALSE;
                }
                long num = atomicLong.incrementAndGet();
                // 重试N次操作控制
                if(num > retryFallbackTimes){
                    log.info("over the retryFallbackTimes {} not will the retry execute!",retryFallbackTimes);
                    // 当无法在进行重试后，便会进行设置为 -1 ，从而进行控制重试的次数记录：TODO 以后扩展使用！
                    // 用于区分首次进行计算和最终放弃重试的状态标志！
                    atomicLong.set(-1L);
                    if(warning(param)){
                        log.info("execute waring the process !");
                    }
                    return Boolean.FALSE;
                }else{
                    log.info("need the retry the process execute current do counter : {}",num);
                    return Boolean.TRUE;
                }
            } catch (Exception e) {
                log.error("should retry the process is having exception!",e);
                return Boolean.FALSE;
            }
        }
        // 如果成功直接返回false不执行重试操作
        log.info("should not execute the retry process!");
        return Boolean.FALSE;
    }


    /**
     * 是否应该进行告警处理
     * 内部包含预警逻辑操作处理控制
     * @param param
     * @return
     */
    @Override
    public boolean warning(FallbackDomain param) {
        //TODO 待完成相关的预警或者告警操作机制处理！
        return false;
    }


}
